package chartCraft.model;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Point;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Observable;

import chartCraft.Log;
import chartCraft.graphDesk.DraggableChart;
import chartCraft.model.Model.NotifyingMessage;

public class LocationModel extends Model{

	private static final long serialVersionUID = 1L;

	public static final String SELECTION_STATUS = "selection status";
	public static final String MOVE = "move";
	public static final String RESIZE = "resize";
	public static final String DATA_CHANGE = "data changes";
	public static final String NAME_CHANGE = "name changes";
	public static final String STUCTURE_CHANGE = "structure changes";
	public static final String NO_OF_TIME_CHANGED = "no of time changed";
	public static final String LOCATION_TIME_UPDATED = "location time changed";
	public static final String BOUND_CHANGED = "bounds changed";
	public static final String PLAY_STATUS_CHANGED = "play status changed";
	public static final String PLAY_FRAME_NO_CHANGED = "player frame no changed";
	public static final String CHART_TYPE_CHANGED = "chart type changed";


	public static final int VERTICAL_BAR_CHART = 0;
	public static final int HORIZONTAL_BAR_CHART = 1;
	public static final int PIE_CHART = 2;
	public static final int VERTICAL_STACKED_BAR_CHART = 3;
	public static final int HORIZONTAL_STACKED_BAR_CHART = 4;

	private Point mCanvasLocation;
	private Dimension mSize;
	private String mName;
	private transient int mTime;
	private double mUpperBoundOfValue;
	private double mLowerBoundOfValue;
	private double mMaxSumOfValueInSameLocation;
	private int mChartType;

	//for the player
	private transient boolean mIsPlaying = false;
	private transient double mPlayingFrameNo = 0;

	/*SeriesData at different time
	 *Instruction on how to use: mSeriesDataAtTime.get(time).get(series);
	 */
	private ArrayList<ArrayList<Double>> mSeriesDataAtTime = new ArrayList<ArrayList<Double>>();
	
	private transient boolean mIsSelect = false; 

	public LocationModel(Point canvasLocation, Dimension size, int chartType) {
		mCanvasLocation = canvasLocation;
		mSize = size;
		mName = "";
		mChartType = chartType;

		//at least have a time zero
		mSeriesDataAtTime.add(new ArrayList<Double>());
	}

	//for the table mainly
	public void addNewTime(ArrayList<Double> seriesOfNewTime) {
		if (seriesOfNewTime == null) {
			int noOfSeries = getNoOfSeries();
			seriesOfNewTime = new ArrayList<Double>();
			for (int i = 0; i < noOfSeries; i++) {
				seriesOfNewTime.add(0.0);
			}
		}

		//All Arraylist in seriesOfNewTime must match
		if (seriesOfNewTime.size() != getNoOfSeries()) {
			Log.e(this, "Wrong size for the supplied seriesOfNewTime");
		}

		mSeriesDataAtTime.add(seriesOfNewTime);

		NotifyingMessage message = new NotifyingMessage(NO_OF_TIME_CHANGED, mName);
		setChanged();
		notifyObservers(message);
	}

	public ArrayList<Double> removeLastTime() {
		int lastElementIndex = getNoOfTime() - 1;
		final ArrayList<Double> datasetBeingRemoved = mSeriesDataAtTime.get(lastElementIndex);
		mSeriesDataAtTime.remove(lastElementIndex);

		NotifyingMessage message = new NotifyingMessage(NO_OF_TIME_CHANGED, mName);
		setChanged();
		notifyObservers(message);

		return datasetBeingRemoved;
	}

	public void setName(String mName) {
		this.mName = mName;

		NotifyingMessage message = new NotifyingMessage(NAME_CHANGE, mName);
		setChanged();
		notifyObservers(message);
	}

	public String getName() {
		return mName;
	}

	public void setValueAt(int time, int series, Double value) {
		if (time < getNoOfTime() && series < getNoOfSeries()) {
			mSeriesDataAtTime.get(time).set(series, value);

			NotifyingMessage message = new NotifyingMessage(DATA_CHANGE, series);
			setChanged();
			notifyObservers(message);
			return;
		}
		Log.e(this, "Invalid time or series specified for \"setValueAt\"");
	}

	public Double getValueAt(int time, int series) {
		if (time < getNoOfTime() && series < getNoOfSeries()) {
			return mSeriesDataAtTime.get(time).get(series);
		}
		Log.e(this, "Invalid time or series specified for \"getValueAt\" with time: " 
				+ time + " and series: " + series);
		return 0.0;
	}

	public int getNoOfTime() {
		return mSeriesDataAtTime.size();
	}

	public void insertNewSeries() {
		for (ArrayList<Double> listOfSeries: mSeriesDataAtTime) {
			listOfSeries.add(0.0); //Default value in cell be 0.0
		}

		NotifyingMessage message = new NotifyingMessage(STUCTURE_CHANGE, mSeriesDataAtTime);
		setChanged();
		notifyObservers(message);
	}
	
	public void insertSeriesWithValue(ArrayList<Double> values) {
		if (values.size() != mSeriesDataAtTime.size()) {
			Log.e(this, "Mismatched size of values given.");
		}
		
		for (int time = 0; time < mSeriesDataAtTime.size(); time++) {
			ArrayList<Double> listOfSeries = mSeriesDataAtTime.get(time);
			listOfSeries.add(values.get(time));
		}
		
		NotifyingMessage message = new NotifyingMessage(STUCTURE_CHANGE, mSeriesDataAtTime);
		setChanged();
		notifyObservers(message);
	}

	public ArrayList<Double> deleteLastSeries() {
		ArrayList<Double> valuesRemoved = new ArrayList<Double>();
		for (ArrayList<Double> listOfSeries: mSeriesDataAtTime) {
			final int lastIndex = listOfSeries.size() - 1;
			valuesRemoved.add(listOfSeries.remove(lastIndex));
		}	

		NotifyingMessage message = new NotifyingMessage(STUCTURE_CHANGE, mSeriesDataAtTime);
		setChanged();
		notifyObservers(message);
		
		return valuesRemoved;
	}

	public int getNoOfSeries() {
		if (mSeriesDataAtTime.size() > 0) {
			return mSeriesDataAtTime.get(0).size();
		}
		return 0;
	}

	public void setSelect(boolean isSelect) {
		final boolean oldValue = mIsSelect;
		mIsSelect = isSelect;

		if (oldValue != isSelect) {
			//value changed
			NotifyingMessage message = new NotifyingMessage(SELECTION_STATUS, isSelect);
			setChanged();
			notifyObservers(message); 	
		}
	}

	public boolean isSelect() {
		return mIsSelect;
	}

	//for the graphing
	public void setLocation(Point location) {
		mCanvasLocation = location;

		NotifyingMessage message = new NotifyingMessage(MOVE, location);
		setChanged();
		notifyObservers(message);
	}

	public Point getCanvasLocation() {
		return mCanvasLocation;
	}

	public void setSize(Dimension size) {
		mSize = size;

		NotifyingMessage message = new NotifyingMessage(RESIZE, size);
		setChanged();
		notifyObservers(message);
	}

	public Dimension getSize() {
		return mSize;
	}

	public void setTime(int time) {
		mTime = time;

		NotifyingMessage message = new NotifyingMessage(LOCATION_TIME_UPDATED, time);
		setChanged();
		notifyObservers(message);
	}

	public int getTime() {
		return mTime;
	}

	public void setChartType(int chartType) {
		if (chartType != mChartType) {
			mChartType = chartType;

			NotifyingMessage message = new NotifyingMessage(CHART_TYPE_CHANGED, mChartType);
			setChanged();
			notifyObservers(message);
		}

	}

	public int getChartType() {
		return mChartType;
	}

	public void setUpperBoundOfValue(double upperBoundOfValue) {
		if (upperBoundOfValue != mUpperBoundOfValue) {
			mUpperBoundOfValue = upperBoundOfValue;

			NotifyingMessage message = new NotifyingMessage(BOUND_CHANGED, mUpperBoundOfValue);
			setChanged();
			notifyObservers(message);
		}
	}

	public double getUpperBoundOfValue() {
		return mUpperBoundOfValue;
	}

	public void setLowerBoundOfValue(double lowerBoundOfValue) {
		if (lowerBoundOfValue != mLowerBoundOfValue) {
			mLowerBoundOfValue = lowerBoundOfValue;

			NotifyingMessage message = new NotifyingMessage(BOUND_CHANGED, mUpperBoundOfValue);
			setChanged();
			notifyObservers(message);
		}
	}

	public double getLowerBoundOfValue() {
		return mLowerBoundOfValue;
	}


	public void setPlaying(boolean flag) {
		if (mIsPlaying != flag) {
			mIsPlaying = flag;

			NotifyingMessage message = new NotifyingMessage(PLAY_STATUS_CHANGED, flag);
			setChanged();
			notifyObservers(message);
		}
	}

	public boolean isPlaying() {
		return mIsPlaying;
	}

	public double getFrameNo() {
		return mPlayingFrameNo;
	}

	public void setFrameNo(double frameNo) {
		mPlayingFrameNo = frameNo;

		NotifyingMessage message = new NotifyingMessage(PLAY_FRAME_NO_CHANGED, frameNo);
		setChanged();
		notifyObservers(message);
	}
	
	public void setMaxSumOfValueInSameLocation(double newValue) {
		if (mMaxSumOfValueInSameLocation != newValue) {
			mMaxSumOfValueInSameLocation = newValue;
			
			NotifyingMessage message = new NotifyingMessage(BOUND_CHANGED, mMaxSumOfValueInSameLocation);
			setChanged();
			notifyObservers(message);
		}
	}

	public double getMaxSumOfValueInSameLocation() {
		return mMaxSumOfValueInSameLocation;
	}
}